package comint03.parser;

import java.io.IOException;
import java.io.Reader;

import java.util.ArrayList;
import java.util.List;

import comint03.evaluator.Array;
import comint03.evaluator.Assoc;
import comint03.evaluator.Dict;
import comint03.evaluator.Ident;
import comint03.evaluator.Nil;
import comint03.evaluator.Num;
import comint03.evaluator.Ref;
import comint03.evaluator.Str;
import comint03.evaluator.Value;

import comint03.tokenizer.Token;
import comint03.tokenizer.Token.Type;
import comint03.tokenizer.Tokenizer;

public class Parser {

	private Tokenizer tokenizer;
	private Token token;
//	private List<Ident> tags = new ArrayList<Ident>();

	public Parser(Reader in){
		tokenizer = new Tokenizer(in);
	}

	private void next() throws IOException{
		token = tokenizer.next();
		System.out.println(token.type() + " ");
	}

	public Value parse() throws IOException, ParserException{
		next();
		Value root = parseValue();
		next();
		if (token.type() != Type.Eos) {
			throw new ParserException("PARSER: expected EOS, found " + token.type() + ".");
		}
		return root;
	}

	private Value parseValue() throws IOException, ParserException {
		Value v = null;
		if (token.type() == Type.OpenParen){		// [
			v = parseArray();
			if (token.type() != Type.CloseParen)	// ]
				throw new ParserException("PARSER: expected CloseParen, found " + token.type() + ".");
		} else if (token.type() == Type.OpenBrace){	// {
			v = parseDictonary();
			if (token.type() != Type.CloseBrace)	// }
				throw new ParserException("PARSER: expected CloseBrace, found " + token.type() + ".");
		} else if (token.type() == Type.Tag){		// #
			v = parseTag();
		} else {
			v = parseValue();						// !Dict !Array !Tag
		}
		return v;
	}

	private Value parseDictonary() throws IOException, ParserException {
		Dict dict = new Dict();
		System.out.println("DICTIONARY --->");
		while(token.type() != Type.CloseBrace){
			next();
			switch(token.type()){
			case Ident:
				parseAssoc(dict); break;
			case Semicolon:
			case CloseBrace:
				break;
			default :
				throw new ParserException("PARSER: undefined Ident key, found " + token.type() + ".");
			}
		}
		System.out.println("---> DICTIONARY");
		return dict;
	}

	private Value parseArray() throws IOException, ParserException {
		List<Value> vals = new ArrayList<Value>();
		System.out.println("ARRAY --->");
		while(token.type() != Type.CloseParen){
			next();
			switch(token.type()){
			case Ident:
				vals.add(parseIdent()); break;
			case Num:
				vals.add(parseNum()); break;
			case String:
				vals.add(parseString()); break;
			case OpenBrace:
				vals.add(parseDictonary()); break;
			case OpenParen:
				vals.add(parseArray()); break; 
			case Tag:
				vals.add(parseTag()); break;
			case Ref:
				vals.add(parseRef()); break;
			case Comma :
				vals.add(new Nil()); break;
			}
//			next();
		}
		System.out.println("---> ARRAY");
		return new Array(vals);
	}

	private Value parseIdent() {
		return new Ident(token.name());
	}

	private void parseAssoc(Dict dict) throws IOException, ParserException {
		Value a = null;
		Ident i = new Ident(token.name());
		
		next();
		if (token.type() != Type.Assoc)
			throw new ParserException("PARSER: expected colon, found " + token.type() + ".");
		next();
		
		switch(token.type()){
		case Num:
			a = parseNum(); break;
		case String:
			a = parseString(); break;
		case OpenBrace:
			a = parseDictonary(); break;
		case OpenParen:
			a = parseArray(); break;
		case Tag:
			a = parseTag(); break;
		case Ref:
			a = parseRef(); break;
		default:
			a = new Nil();
		}
		dict.add(i, a);
	}
	
	private Value parseRef() throws IOException, ParserException{
		next();
		if (token.type() == Type.Ident){
			next();
			return new Ref(new Ident(token.name()));
		}
		throw new ParserException("PARSER: expected ident, found " + token.type() + ".");
	}
	
	private Value parseTag() throws IOException, ParserException {
		Value v = null;
		next();
		if (token.type() == Type.Tag)
			v = parseRef();
		return v;
	}

	private Value parseString() throws IOException {
		Value v = new Str(token.name());
		next();
		return v;
	}

	private Value parseNum() throws IOException {
		Value v = new Num(token.num());
		next();
		return v;
	}

}
